/**
 * Copyright 2008 floop.pl
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 *  
 * http://www.apache.org/licenses/LICENSE-2.0 
 *  
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package org.eaicompiler.decompiler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.eaicompiler.antlr.EventAILexer;
import org.eaicompiler.antlr.EventAIParser;
import org.eaicompiler.database.ScriptdevHibernateUtil;
import org.eaicompiler.database.model.EventaiScripts;
import org.eaicompiler.database.model.EventaiSummons;
import org.eaicompiler.database.model.LocalizedTexts;
import org.eaicompiler.model.Action;
import org.eaicompiler.model.Defines;
import org.eaicompiler.model.Event;
import org.eaicompiler.model.SpellSchool;
import org.eaicompiler.model.Target;
import org.eaicompiler.sqlgen.builders.SQLBuilder;
import org.eaicompiler.util.Util;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

/**
 * Script decompiler, woohoo :)
 * 
 * @author floop.pl
 */
public class Decompiler {
	private SessionFactory					sf;
	private Session							session;

	private SortedMap<Integer, List<Event>>	eventsForCreature;
	private boolean							formatting	= true;
	private boolean							newLines	= true;

	public Decompiler() {
		super();
		sf = ScriptdevHibernateUtil.getSessionFactory();
		session = sf.openSession();
		eventsForCreature = new TreeMap<Integer, List<Event>>();
	}

	private List<Event> getEventsForCreature(int id) {
		List<Event> result = null;
		result = eventsForCreature.get(new Integer(id));
		if (result == null) {
			eventsForCreature.put(new Integer(id), new ArrayList<Event>());
			result = eventsForCreature.get(new Integer(id));
		}
		return result;
	}

	@SuppressWarnings("unchecked")
	public String decompile(String selector) {
		StringBuffer creatures = new StringBuffer();
		StringBuffer result = new StringBuffer();
		List list = session.createQuery("from EventaiScripts " + selector + " order by creatureId, event_inverse_phase_mask").list();
		Iterator it = list.iterator();
		while (it.hasNext()) {
			EventaiScripts l = (EventaiScripts) it.next();
			Event e = convertEvent(l);
			getEventsForCreature(l.getCreatureId()).add(e);
		}
		Iterator<Integer> i = eventsForCreature.keySet().iterator();
		while (i.hasNext()) {
			Integer creature = i.next();
			creatures.append(decompileCreature(creature.intValue(), eventsForCreature.get(creature)));
			appendLn(creatures);
		}
		result.append(decompileDefines());
		appendLn(result);
		result.append(creatures);
		return result.toString();
	}

	public String decompileDefines() {
		StringBuffer result = new StringBuffer();
		for (int f = 0; f < Defines.DEFINE_TYPES_COUNT; f++) {
			result.append(decompileDefinesFor(f));
		}
		return result.toString();
	}

	private String decompileDefinesFor(int definesType) {
		StringBuffer result = new StringBuffer();
		Map<String, Integer> dm = Util.getDefines().getDefines(definesType);

		SortedMap<Integer, String> m = new TreeMap<Integer, String>();
		Iterator<String> itS = dm.keySet().iterator();
		while (itS.hasNext()) {
			String d = itS.next();
			m.put(dm.get(d), d);
		}

		Iterator<Integer> itI = m.keySet().iterator();
		while (itI.hasNext()) {
			Integer d = itI.next();
			if ((Util.getDefines().getInverseDefinesUsed(definesType).get(d)) != null) {
				switch (definesType) {
					case Defines.DEFINE_CREATURE: {
						result.append("DEFINE CREATURE ");
					}
					break;
					case Defines.DEFINE_EMOTE: {
						result.append("DEFINE EMOTE ");
					}
					break;
					case Defines.DEFINE_UNIT_FIELD: {
						result.append("DEFINE UNIT FIELD ");
					}
					break;
					case Defines.DEFINE_FLAG: {
						result.append("DEFINE FLAG ");
					}
					break;
					case Defines.DEFINE_QUEST: {
						result.append("DEFINE QUEST ");
					}
					break;
					case Defines.DEFINE_SOUND: {
						result.append("DEFINE SOUND ");
					}
					break;
					case Defines.DEFINE_SPELL: {
						result.append("DEFINE SPELL ");
					}
					break;
					case Defines.DEFINE_INSTANCE_FIELD: {
						result.append("DEFINE INSTANCE FIELD ");
					}
					break;
					case Defines.DEFINE_TEMPLATE: {
						result.append("DEFINE TEMPLATE ");
					}
				}
				result.append(d);
				result.append(" \"");
				result.append(m.get(d));
				result.append("\"");
				appendLn(result);
			}
		}
		return result.toString();
	}

	/**
	 * Method that converts the event from database into internal structure
	 * 
	 * @param Event
	 *            in database
	 * @return Event in internal structure format
	 */
	private Event convertEvent(EventaiScripts from) {
		Event to = new Event();
		to.setChance(from.getEventChance());
		to.setInversePhaseMask(from.getEventInversePhaseMask());
		to.setType(from.getEventType());
		to.setComment(from.getComment());
		to.setParam(0, from.getEventParam1());
		to.setParam(1, from.getEventParam2());
		to.setParam(2, from.getEventParam3());
		to.addAction(createAction(from.getAction1Type(), from.getAction1Param1(), from.getAction1Param2(), from.getAction1Param3()));
		to.addAction(createAction(from.getAction2Type(), from.getAction2Param1(), from.getAction2Param2(), from.getAction2Param3()));
		to.addAction(createAction(from.getAction3Type(), from.getAction3Param1(), from.getAction3Param2(), from.getAction3Param3()));
		return to;
	}

	private Action createAction(int type, int param1, int param2, int param3) {
		Action a = new Action();
		a.setType(type);
		a.setParam(0, param1);
		a.setParam(1, param2);
		a.setParam(2, param3);
		return a;
	}

	private String decompileCreature(int creatureId, List<Event> events) {
		StringBuffer result = new StringBuffer();
		// First, we should calculate maximum phase used
		long mask = 0;
		int maxPhase = 0;
		Iterator<Event> it = events.iterator();
		while (it.hasNext()) {
			Event e = it.next();
			mask = mask | e.getInversePhaseMask();
		}
		if (mask > 0) {
			for (int f = 0; f < 32; f++) {
				if ((mask & 1 << f) > 0) {
					maxPhase = f + 1;
				}
			}
		}
		long currentMask = -1;

		result.append("SCRIPT FOR CREATURE ");
		result.append(creatureId);
		appendLn(result);
		
		it = events.iterator();
		while (it.hasNext()) {
			Event e = it.next();
			if (e.getInversePhaseMask() != currentMask) {
				if (e.getInversePhaseMask() == 0) {
					result.append("PHASE ALL");
					appendLn(result);
				} else {
					result.append("PHASE ");
					result.append(calculatePhase(e.getInversePhaseMask(), maxPhase));
					appendLn(result);
				}
				currentMask = e.getInversePhaseMask();
			}
			result.append(decompileEvent(e));
		}

		return result.toString();
	}

	private String calculatePhase(long phase, int maxPhase) {
		StringBuffer result = new StringBuffer();
		boolean hasPhase = false;
		for (int f = 0; f < maxPhase; f++) {
			if ((phase & 1 << f) == 0) {
				if (hasPhase) {
					result.append(", ");
				}
				result.append(f + 1);
				hasPhase = true;
			}
		}
		if(!hasPhase)
		{
			result.append(maxPhase+1);
		}
		return result.toString();
	}

	public String decompileEvent(Event e) {
		StringBuffer result = new StringBuffer();
		appendTab(result);
		result.append("EVENT ");
		if (e.getChance() < 100) {
			result.append("CHANCE ");
			result.append(e.getChance());
			result.append("% ");
		}
		result.append("ON ");
		switch (e.getType()) {
			case Event.EVENT_T_AGGRO: {
				result.append("AGGRO");
			}
			break;
			case Event.EVENT_T_DEATH: {
				result.append("DEATH");
			}
			break;
			case Event.EVENT_T_EVADE: {
				result.append("EVADE");
			}
			break;
			case Event.EVENT_T_HP: {
				result.append("HP ");
				result.append(convertToPercent(e.getParam(0)));
				if (e.getParam(1) > 0) {
					result.append(" FROM ");
					result.append(convertToPercent(e.getParam(1)));
				}
				if (e.getParam(2) > 0) {
					result.append(" EVERY ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_TARGET_HP: {
				result.append("TARGET HP ");
				result.append(convertToPercent(e.getParam(0)));
				if (e.getParam(1) > 0) {
					result.append(" FROM ");
					result.append(convertToPercent(e.getParam(1)));
				}
				if (e.getParam(2) > 0) {
					result.append(" EVERY ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_KILL: {
				result.append("KILL");
				if (e.getParam(0) > 0) {
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(0)));
				}
			}
			break;
			case Event.EVENT_T_MANA: {
				result.append("MANA ");
				result.append(convertToPercent(e.getParam(0)));
				if (e.getParam(1) > 0) {
					result.append(" FROM ");
					result.append(convertToPercent(e.getParam(1)));
				}
				if (e.getParam(2) > 0) {
					result.append(" EVERY ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_SPELLHIT: {
				result.append("SPELLHIT");
				if (e.getParam(2) > 0) {
					result.append(" EVERY ");
					result.append(convertToTime(e.getParam(2)));
				}
				if (e.getParam(0) > 0) {
					result.append(" SPELL ");
					String spell = Util.inverseResolve(Defines.DEFINE_SPELL, e.getParam(0));
					result.append(spell);
				}
				if (e.getParam(1) > 0) {
					result.append(" SCHOOL");
					if ((e.getParam(1) & SpellSchool.ARCANE) != 0) {
						result.append(" ARCANE");
					}
					if ((e.getParam(1) & SpellSchool.FIRE) != 0) {
						result.append(" FIRE");
					}
					if ((e.getParam(1) & SpellSchool.FROST) != 0) {
						result.append(" FROST");
					}
					if ((e.getParam(1) & SpellSchool.HOLY) != 0) {
						result.append(" HOLY");
					}
					if ((e.getParam(1) & SpellSchool.NATURE) != 0) {
						result.append(" NATURE");
					}
					if ((e.getParam(1) & SpellSchool.NORMAL) != 0) {
						result.append(" NORMAL");
					}
					if ((e.getParam(1) & SpellSchool.SHADOW) != 0) {
						result.append(" SHADOW");
					}
				}
			}
			break;
			case Event.EVENT_T_TIMER_OOC_REPEAT: {
				result.append("TIMER OOC REPEAT EVERY ");
				result.append(convertToTime(e.getParam(0)));
				if (e.getParam(1) > 0) {
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(1)));
				}
				if (e.getParam(2) > 0) {
					result.append(" RANDOM ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_TIMER_OOC_SINGLE: {
				result.append("TIMER OOC SINGLE");
				if (e.getParam(0) > 0) {
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(0)));
				}
			}
			break;
			case Event.EVENT_T_TIMER_REPEAT: {
				result.append("TIMER REPEAT EVERY ");
				result.append(convertToTime(e.getParam(0)));
				if (e.getParam(1) > 0) {
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(1)));
				}
				if (e.getParam(2) > 0) {
					result.append(" RANDOM ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_TIMER_SINGLE: {
				result.append("TIMER SINGLE");
				if (e.getParam(0) > 0) {
					result.append(" AFTER ");
					result.append(convertToTime(e.getParam(0)));
				}
			}
			break;
			case Event.EVENT_T_RANGE: {
				result.append("RANGE");
				if (e.getParam(1) > 0) {
					result.append(" FROM ");
					result.append(e.getParam(1));
				}
				result.append(" TO ");
				result.append(e.getParam(0));
				if (e.getParam(2) > 0) {
					result.append(" EVERY ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_OOC_LOS: {
				result.append("OOC LOS");
				if (e.getParam(0) > 0) {
					result.append(" NOHOSTILE");
				}
				if (e.getParam(1) > 0) {
					result.append(" NOFRIENDLY");
				}
				if (e.getParam(2) > 0) {
					result.append(" EVERY ");
					result.append(convertToTime(e.getParam(2)));
				}
			}
			break;
			case Event.EVENT_T_SPAWNED: {
				result.append("SPAWNED");
			}
			break;
			case Event.EVENT_T_TARGET_CASTING: {
				result.append("TARGET CASTING");
				if (e.getParam(0) > 0) {
					result.append(" EVERY ");
					result.append(convertToTime(e.getParam(0)));
				}
			}
			break;
			case Event.EVENT_T_FRIENDLY_HP: {
				result.append("FRIENDLY HP DEFICIT ");
				result.append(e.getParam(0));
				result.append(" RANGE ");
				result.append(e.getParam(1));
				if(e.getParam(2)>0)
				{
					result.append(" EVERY ");
					result.append(convertToTime(e.getParam(2)));					
				}
			}
			break;
			default: {
				System.err.println("+++ ERROR UNSUPPORTED EVENT " + e.getType());
			}
			break;
		}
		appendLn(result);
		if (e.getComment() != null && e.getComment().length() > 0) {
			appendTab(result);
			appendTab(result);
			result.append("COMMENT \"");
			result.append(Util.quoteString(e.getComment()));
			result.append("\"");
			appendLn(result);
		}
		for (int f = 0; f < Event.MAX_EVENT_ACTIONS; f++) {
			result.append(decompileAction(e.getAction(f)));
		}

		appendTab(result);
		result.append("END");
		appendLn(result);
		return result.toString();
	}

	private void appendTab(StringBuffer result) {
		if (formatting) {
			result.append("\t");
		}
	}

	private void appendLn(StringBuffer result) {
		if (newLines) {
			result.append("\n");
		}
	}

	public String decompileAction(Action action) {
		StringBuffer s = new StringBuffer();
		if (action.getType() > 0) {
			appendTab(s);
			appendTab(s);
			switch (action.getType()) {
				case Action.ACTION_T_SAY: {
					s.append("SAY ");
					s.append(getDecompiledText(action.getParam(0)));
				}
				break;
				case Action.ACTION_T_RANDOM_SAY: {
					s.append("SAY ");
					s.append(getDecompiledText(action.getParam(0)));
					if (action.getParam(1) != -1) {
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(1)));
					}
					if (action.getParam(2) != -1) {
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(2)));
					}
				}
				break;
				case Action.ACTION_T_YELL: {
					s.append("YELL ");
					s.append(getDecompiledText(action.getParam(0)));
				}
				break;
				case Action.ACTION_T_RANDOM_YELL: {
					s.append("YELL ");
					s.append(getDecompiledText(action.getParam(0)));
					if (action.getParam(1) != -1) {
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(1)));
					}
					if (action.getParam(2) != -1) {
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(2)));
					}
				}
				break;
				case Action.ACTION_T_TEXTEMOTE: {
					s.append("TEXT EMOTE ");
					s.append(getDecompiledText(action.getParam(0)));
				}
				break;
				case Action.ACTION_T_RANDOM_TEXTEMOTE: {
					s.append("TEXT EMOTE ");
					s.append(getDecompiledText(action.getParam(0)));
					if (action.getParam(1) != -1) {
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(1)));
					}
					if (action.getParam(2) != -1) {
						s.append(" OR ");
						s.append(getDecompiledText(action.getParam(2)));
					}
				}
				break;
				case Action.ACTION_T_AUTO_ATTACK: {
					s.append("AUTO ATTACK ");
					if (action.getParam(0) == 0) {
						s.append("OFF");
					} else {
						s.append("ON");
					}
				}
				break;
				case Action.ACTION_T_CAST: {
					s.append("CAST ");
					String spell = Util.inverseResolve(Defines.DEFINE_SPELL, action.getParam(0));
					s.append(spell);
					s.append(" ");
					if ((action.getParam(2) & 1) != 0) {
						s.append("INTERRUPT ");
					}
					if ((action.getParam(2) & 2) != 0) {
						s.append("TRIGGERED ");
					}
					if ((action.getParam(2) & 4) != 0) {
						s.append("FORCE ");
					}
					if ((action.getParam(2) & 8) != 0) {
						s.append("NO_MELEE_IF_OOM ");
					}
					if ((action.getParam(2) & 16) != 0) {
						s.append("FORCE_TARGET_SELF ");
					}
					s.append(decompileTargetCode(action.getParam(1)));
				}
				break;
				case Action.ACTION_T_QUEST_COMPLETE: {
					s.append("QUEST COMPLETE ");
					String quest = Util.inverseResolve(Defines.DEFINE_QUEST, action.getParam(0));
					s.append(quest);
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(1)));
				}
				break;
				case Action.ACTION_T_QUEST_COMPLETE_ALL: {
					s.append("QUEST COMPLETE ");
					String quest = Util.inverseResolve(Defines.DEFINE_QUEST, action.getParam(0));
					s.append(quest);
					s.append(" ON ALL");
				}
				break;
				case Action.ACTION_T_CASTCREATUREGO: {
					s.append("CAST CREATURE GO ");
					String quest = Util.inverseResolve(Defines.DEFINE_QUEST, action.getParam(0));
					s.append(quest);
					s.append(" SPELL ");
					String spell = Util.inverseResolve(Defines.DEFINE_SPELL, action.getParam(1));
					s.append(spell);
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(2)));
				}
				break;
				case Action.ACTION_T_CASTCREATUREGO_ALL: {
					s.append("CAST CREATURE GO ");
					String quest = Util.inverseResolve(Defines.DEFINE_QUEST, action.getParam(0));
					s.append(quest);
					s.append(" SPELL ");
					String spell = Util.inverseResolve(Defines.DEFINE_SPELL, action.getParam(1));
					s.append(spell);
					s.append(" ALL");
				}
				break;
				case Action.ACTION_T_COMBAT_MOVEMENT: {
					s.append("COMBAT MOVEMENT ");
					if (action.getParam(0) == 0) {
						s.append("OFF");
					} else {
						s.append("ON");
					}
				}
				break;
				case Action.ACTION_T_EMOTE: {
					s.append("EMOTE ");
					String e = Util.inverseResolve(Defines.DEFINE_EMOTE, action.getParam(0));
					s.append(e);
				}
				break;
				case Action.ACTION_T_RANDOM_EMOTE: {
					s.append("EMOTE ");
					String e = Util.inverseResolve(Defines.DEFINE_EMOTE, action.getParam(0));
					s.append(e);
					if (action.getParam(1) != -1) {
						s.append(" OR ");
						e = Util.inverseResolve(Defines.DEFINE_EMOTE, action.getParam(1));
						s.append(e);
					}
					if (action.getParam(2) != -1) {
						s.append(" OR ");
						e = Util.inverseResolve(Defines.DEFINE_EMOTE, action.getParam(2));
						s.append(e);
					}
				}
				break;
				case Action.ACTION_T_EVADE: {
					s.append("EVADE");
				}
				break;
				case Action.ACTION_T_FLEE: {
					s.append("FLEE");
				}
				break;
				case Action.ACTION_T_INC_PHASE: {
					if (action.getParam(0) > 0) {
						s.append("SET PHASE INC ");
						s.append(action.getParam(0));
					} else {
						s.append("SET PHASE DEC ");
						s.append(-action.getParam(0));
					}
				}
				break;
				case Action.ACTION_T_SET_PHASE: {
					s.append("SET PHASE TO ");
					s.append(action.getParam(0) + 1);
				}
				break;
				case Action.ACTION_T_SET_UNIT_FIELD: {
					s.append("SET UNIT FIELD ");
					String f = Util.inverseResolve(Defines.DEFINE_UNIT_FIELD, action.getParam(0));
					s.append(f);
					s.append(" TO ");
					s.append(action.getParam(1));
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(2)));
				}
				break;
				case Action.ACTION_T_SET_UNIT_FLAG: {
					s.append("SET UNIT FLAG ");
					StringBuffer flagsList = new StringBuffer();
					boolean hadFlag = false;
					for (int f = 0; f < 32; f++) {
						if ((action.getParam(0) & (1 << f)) > 0) {
							if (hadFlag) {
								flagsList.append(", ");
							}
							String fl = Util.inverseResolve(Defines.DEFINE_FLAG, (1 << f));
							flagsList.append(fl);
							hadFlag = true;
						}
					}
					s.append(flagsList);
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(1)));
				}
				break;
				case Action.ACTION_T_REMOVE_UNIT_FLAG: {
					s.append("REMOVE UNIT FLAG ");
					StringBuffer flagsList = new StringBuffer();
					boolean hadFlag = false;
					for (int f = 0; f < 32; f++) {
						if ((action.getParam(0) & (1 << f)) == (1 << f)) {
							if (hadFlag) {
								flagsList.append(", ");
							}
							String fl = Util.inverseResolve(Defines.DEFINE_FLAG, (1 << f));
							flagsList.append(fl);
							hadFlag = true;
						}
					}
					s.append(flagsList);
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(1)));
				}
				break;
				case Action.ACTION_T_SOUND: {
					s.append("SOUND ");
					String e = Util.inverseResolve(Defines.DEFINE_SOUND, action.getParam(0));
					s.append(e);
				}
				break;
				case Action.ACTION_T_RANDOM_SOUND: {
					s.append("SOUND ");
					String e = Util.inverseResolve(Defines.DEFINE_SOUND, action.getParam(0));
					s.append(e);
					if (action.getParam(1) != -1) {
						s.append(" OR ");
						e = Util.inverseResolve(Defines.DEFINE_SOUND, action.getParam(1));
						s.append(e);
					}
					if (action.getParam(2) != -1) {
						s.append(" OR ");
						e = Util.inverseResolve(Defines.DEFINE_SOUND, action.getParam(2));
						s.append(e);
					}
				}
				break;
				case Action.ACTION_T_SUMMON: {
					s.append("SUMMON ");
					String e = Util.inverseResolve(Defines.DEFINE_CREATURE, action.getParam(0));
					s.append(e);
					s.append(" ");
					s.append(decompileTargetCode(action.getParam(1)));
					if (action.getParam(2) > 0) {
						s.append(" FOR ");
						s.append(convertToTime(action.getParam(2)));
					}
				}
				break;
				case Action.ACTION_T_THREAT_ALL_PCT: {
					s.append("THREAT ALL ");
					s.append(convertToPercent(action.getParam(0)));
				}
				break;
				case Action.ACTION_T_THREAT_SINGLE_PCT: {
					s.append("THREAT SINGLE ");
					s.append(convertToPercent(action.getParam(0)));
					s.append(" ");
					s.append(decompileTargetCode(action.getParam(1)));
				}
				break;
				case Action.ACTION_T_REMOVEAURASFROMSPELL: {
					s.append("REMOVE AURA FROM SPELL ");
					String spell = Util.inverseResolve(Defines.DEFINE_SPELL, action.getParam(1));
					s.append(spell);
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(0)));
				}
				break;
				case Action.ACTION_T_RANGED_MOVEMENT: {
					s.append("RANGED MOVEMENT ");
					if (action.getParam(0) == 0 && action.getParam(1) == 0) {
						s.append("OFF");
					} else {
						s.append(action.getParam(0));
						s.append(", ");
						s.append(action.getParam(1));
					}
				}
				break;
				case Action.ACTION_T_RANDOM_PHASE: {
					s.append("SET PHASE RANDOM TO ");
					s.append(action.getParam(0) + 1);
					if (action.getParam(1) != -1) {
						s.append(" OR ");
						s.append(action.getParam(1) + 1);
					}
					if (action.getParam(2) != -1) {
						s.append(" OR ");
						s.append(action.getParam(2) + 1);
					}
				}
				break;
				case Action.ACTION_T_RANDOM_PHASE_RANGE: {
					s.append("SET PHASE RANDOM FROM ");
					s.append(action.getParam(0) + 1);
					s.append(" TO ");
					s.append(action.getParam(1) + 1);
				}
				break;
				case Action.ACTION_T_SUMMON_ID: {
					s.append("SUMMON ");
					String e = Util.inverseResolve(Defines.DEFINE_CREATURE, action.getParam(0));
					s.append(e);
					s.append(" ");
					s.append(decompileTargetCode(action.getParam(1)));
					s.append(" AT ");
					s.append(getDecompiledSummon(action.getParam(2)));
				}
				break;
				case Action.ACTION_T_KILLED_MONSTER: {
					s.append("KILLED MONSTER ");
					String e = Util.inverseResolve(Defines.DEFINE_CREATURE, action.getParam(0));
					s.append(e);
					s.append(" ON ");
					s.append(decompileTargetCode(action.getParam(1)));
				}
				break;
				case Action.ACTION_T_SET_INST_DATA: {
					s.append("SET INSTANCE DATA ");
					String e = Util.inverseResolve(Defines.DEFINE_INSTANCE_FIELD, action.getParam(0));
					s.append(e);
					s.append(" TO VALUE ");
					s.append(action.getParam(1));
				}
				break;
				case Action.ACTION_T_SET_INST_DATA64: {
					s.append("SET INSTANCE DATA ");
					String e = Util.inverseResolve(Defines.DEFINE_INSTANCE_FIELD, action.getParam(0));
					s.append(e);
					s.append(" TO ");
					s.append(decompileTargetCode(action.getParam(1)));
				}
				break;
				case Action.ACTION_T_UPDATE_TEMPLATE: {
					s.append("UPDATE TEMPLATE TO ");
					String e = Util.inverseResolve(Defines.DEFINE_TEMPLATE, action.getParam(0));
					s.append(e);
					if(action.getParam(1)==1)
					{
						s.append(" HORDE");
					} else {
						s.append(" ALLIANCE");						
					}
				}
				break;
				default: {
					System.err.println("+++ ERROR UNSUPPORTED ACTION " + action.getType());
				}
				break;
			}
			appendLn(s);
		}
		return s.toString();
	}

	public String decompileTargetCode(int param) {
		switch (param) {
			case Target.TARGET_T_CASTER:
				return "TARGET CASTER";
			case Target.TARGET_T_HOSTILE:
				return "TARGET HOSTILE";
			case Target.TARGET_T_HOSTILE_LAST_AGGRO:
				return "TARGET HOSTILE LAST AGGRO";
			case Target.TARGET_T_HOSTILE_RANDOM:
				return "TARGET HOSTILE RANDOM";
			case Target.TARGET_T_HOSTILE_RANDOM_NOT_TOP:
				return "TARGET HOSTILE RANDOM NOT TOP";
			case Target.TARGET_T_HOSTILE_SECOND_AGGRO:
				return "TARGET HOSTILE SECOND AGGRO";
			case Target.TARGET_T_SELF:
				return "TARGET SELF";
		}
		return "UNKNOWN TARGET TYPE";
	}

	private String convertToTime(int param) {
		StringBuffer s = new StringBuffer();
		if (param % 1000 == 0) {
			s.append(Integer.toString(param / 1000));
			s.append(" s");
		} else {
			s.append(Integer.toString(param));
			s.append(" ms");
		}
		return s.toString();
	}

	private String convertToPercent(int param) {
		StringBuffer s = new StringBuffer();
		s.append(Integer.toString(param));
		s.append("%");
		return s.toString();
	}

	private String getDecompiledText(int text) {
		StringBuffer result = new StringBuffer();
		LocalizedTexts t = (LocalizedTexts) session.get(LocalizedTexts.class, new Integer(text));
		if (t != null) {
			result.append("\"");
			result.append(SQLBuilder.escapeStringForScript(t.getLocale0()));
			result.append("\"");
			if (t.getLocale2() != null && t.getLocale2().length() > 0) {
				result.append(",FRENCH=\"");
				result.append(SQLBuilder.escapeStringForScript(t.getLocale2()));
				result.append("\"");
			}
			if (t.getLocale3() != null && t.getLocale3().length() > 0) {
				result.append(",GERMAN=\"");
				result.append(SQLBuilder.escapeStringForScript(t.getLocale3()));
				result.append("\"");
			}
		} else {
			return "\"!!!!Undefined text!!!!\"";
		}
		return result.toString();
	}

	private String getDecompiledSummon(int summon) {
		StringBuffer result = new StringBuffer();
		EventaiSummons s = (EventaiSummons) session.get(EventaiSummons.class, new Integer(summon));
		if (s != null) {
			result.append("");
			result.append(s.getX());
			result.append(", ");
			result.append(s.getY());
			result.append(", ");
			result.append(s.getZ());
			result.append(", ");
			result.append(s.getO());
			if (s.getTime() > 0) {
				result.append(" FOR ");
				result.append(convertToTime(s.getTime()));
			}
		} else {
			return "\"!!!!Undefined text!!!!\"";
		}
		return result.toString();
	}

	public void setNoFormatting() {
		this.formatting = false;
	}

	public void setNoNewLines() {
		this.newLines = false;
	}

	/**
	 * Registers include file for decompiler usage
	 * 
	 * @param include
	 *            Include file name
	 * @throws RecognitionException
	 */
	public void addInclude(String include) throws RecognitionException {
		try {
			CharStream tmp_input = new ANTLRFileStream(include);
			EventAILexer tmp_lex = new EventAILexer(tmp_input);
			CommonTokenStream tmp_tokens = new CommonTokenStream(tmp_lex);
			EventAIParser tmp_parser = new EventAIParser(tmp_tokens);
			tmp_parser.script();
		} catch (IOException e) {
			throw new RuntimeException("File not found: " + include);
		}
	}
}
